package top.wshape1.shiziapi.project.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.wshape1.shiziapi.common.constant.BizCodeEnum;
import top.wshape1.shiziapi.common.constant.RedisConstant;
import top.wshape1.shiziapi.common.exception.CustomException;
import top.wshape1.shiziapi.common.utils.R;
import top.wshape1.shiziapi.project.common.constant.DocumentConstant;
import top.wshape1.shiziapi.project.domain.Api;
import top.wshape1.shiziapi.project.domain.ApiContent;
import top.wshape1.shiziapi.project.domain.ApiTag;
import top.wshape1.shiziapi.project.domain.Project;
import top.wshape1.shiziapi.project.domain.vo.*;
import top.wshape1.shiziapi.project.handler.LoginContextHandler;
import top.wshape1.shiziapi.project.mapper.ApiContentMapper;
import top.wshape1.shiziapi.project.mapper.ApiMapper;
import top.wshape1.shiziapi.project.mapper.ApiTagMapper;
import top.wshape1.shiziapi.project.service.ApiContentService;
import top.wshape1.shiziapi.project.service.ApiService;
import top.wshape1.shiziapi.project.service.ProjectService;
import top.wshape1.shiziapi.project.util.HotProjectUtil;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author Wshape1
 * @description 针对表【t_project_api】的数据库操作Service实现
 * @since 2023-10-22 18:21:51
 */
@Service
public class ApiServiceImpl extends ServiceImpl<ApiMapper, Api>
        implements ApiService {

    @Resource
    private ApiContentMapper apiContentMapper;

    @Resource
    private ApiContentService apiContentService;

    @Resource
    private ApiTagMapper apiTagMapper;

    @Resource
    private ProjectService projectService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Transactional
    @Override
    public Long create(CreateDocVo createDocVo) {
        Api api = new Api();
        api.setProjectId(createDocVo.getProjectId());
        api.setName(createDocVo.getName());
        api.setParentFolderId(createDocVo.getParentFolderId());
        api.setOrder(createDocVo.getOrder());
        api.setCreateTime(new Date());

        this.save(api);

        ApiContent reqParamContent = new ApiContent(null,
                api.getId(),
                DocumentConstant.REQ_PARAM_CONTENT_LABEL,
                DocumentConstant.PARAM_API_CONTENT_TYPE,
                0,
                "[]",
                null
        );
        ApiContent resParamContent = new ApiContent(null,
                api.getId(),
                DocumentConstant.RES_PARAM_CONTENT_LABEL,
                DocumentConstant.PARAM_API_CONTENT_TYPE,
                1,
                "[]",
                null
        );
        ApiContent descContent = new ApiContent(null,
                api.getId(),
                DocumentConstant.DESC_CONTENT_LABEL,
                DocumentConstant.DESC_API_CONTENT_TYPE,
                2,
                null,
                null
        );
        // 插入默认内容
        apiContentMapper.insert(reqParamContent);
        apiContentMapper.insert(resParamContent);
        apiContentMapper.insert(descContent);

        return api.getId();
    }

    @Override
    public void rename(Long id, String name) {

        this.update(new LambdaUpdateWrapper<Api>()
                .eq(Api::getId, id)
                .set(Api::getName, name));

    }

    @Transactional
    @Override
    public void delete(Long id) {
        // api id
        // 删除api文档和其内容

        apiContentMapper.delete(new LambdaQueryWrapper<ApiContent>()
                .eq(ApiContent::getApiId, id));

        //  Tag
        apiTagMapper.delete(new LambdaQueryWrapper<ApiTag>()
                .eq(ApiTag::getApiId, id));

        this.removeById(id);
    }

    @Transactional
    @Override
    public void deleteByParentFolderId(Long parentFolderId) {
        List<Long> ids = this.getIdsByParentFolderId(parentFolderId);

        if (ids != null && !ids.isEmpty())
            apiContentMapper.deleteBatchIds(ids);

        this.remove(new LambdaQueryWrapper<Api>()
                .eq(Api::getParentFolderId, parentFolderId));

    }

    @Override
    public List<Long> getIdsByParentFolderId(Long parentFolderId) {
        return this.getBaseMapper().selectIdsByParentFolderId(parentFolderId);
    }

    @Override
    public void updateOrderAndParentFolderId(Long id, Integer order, Long parentFolderId) {
        this.update(new LambdaUpdateWrapper<Api>()
                .eq(Api::getId, id)
                .set(Api::getOrder, order)
                .set(Api::getParentFolderId, parentFolderId)
        );
    }

    @Override
    public void updateOrder(Long id, Integer order) {
        this.update(new LambdaUpdateWrapper<Api>()
                .eq(Api::getId, id)
                .set(Api::getOrder, order)
        );
    }

    @Override
    public Long createTag(CreateTagVo tagVo, Long userId) {

        if (!projectService.hasPermissionToEdit(tagVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        top.wshape1.shiziapi.project.domain.ApiTag apiTag = new top.wshape1.shiziapi.project.domain.ApiTag();
        apiTag.setApiId(tagVo.getApiId());
        apiTag.setName(tagVo.getName());
        apiTag.setColor(tagVo.getColor());
        int insert = apiTagMapper.insert(apiTag);

        // 更新热点项目缓存
        if (insert > 0) {
            updateTagCacheIfHot(tagVo.getProjectId(), tagVo.getApiId());
        }

        return apiTag.getId();
    }

    @Override
    public void removeTag(TwoIdVo twoIdVo, Long userId) {

        if (!projectService.hasPermissionToEdit(twoIdVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        int i = apiTagMapper.deleteById(twoIdVo.getId());

        // 更新热点项目缓存
        if (i > 0) {
            updateTagCacheIfHot(twoIdVo.getProjectId(), twoIdVo.getId());
        }

    }

    /**
     * 更新热点项目的API TAG缓存
     *
     * @param projectId
     * @param apiId
     */
    private void updateTagCacheIfHot(Long projectId, Long apiId) {
        List<ApiTag> apiTags = listTag(new TwoIdVo(apiId, projectId), LoginContextHandler.getUserId());
        HotProjectUtil.updateCacheIfHot(redisTemplate, projectId,
                RedisConstant.PROJECT_HOT_APITAG_KEY + apiId, apiTags);
    }

    @Override
    public List<ApiTag> listTag(TwoIdVo twoIdVo, Long userId) {

        // 先查询是否是热点项目
        Object o = redisTemplate.opsForHash().get(RedisConstant.PROJECT_HOT_KEY + twoIdVo.getProjectId(),
                RedisConstant.PROJECT_HOT_APITAG_KEY + twoIdVo.getId());
        if (o != null) {
            return (List<ApiTag>) o;
        }

        Project project = projectService.getById(twoIdVo.getProjectId());
        if (project.getIsPrivate() == 1) {
            if (userId == null || !projectService.hasPermissionToEdit(twoIdVo.getProjectId(), userId))
                throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        return apiTagMapper.selectList(new LambdaQueryWrapper<ApiTag>()
                .eq(ApiTag::getApiId, twoIdVo.getId()));
    }

    @Override
    public Long createContent(CreateContentVo createContentVo, Long userId) {
        if (!projectService.hasPermissionToEdit(createContentVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }
        ApiContent apiContent = new ApiContent(
                null,
                createContentVo.getApiId(),
                createContentVo.getLabel(),
                createContentVo.getType(),
                createContentVo.getOrder(),
                createContentVo.getType() == 0 ? "[]" : null,
                null
        );
        apiContentMapper.insert(apiContent);

        // 更新热点项目缓存
        updateContentCacheIfHot(createContentVo.getProjectId(), createContentVo.getApiId());

        return apiContent.getId();
    }

    @Override
    public void removeContent(RemoveContentVo removeContentVo, Long userId) {
        if (!projectService.hasPermissionToEdit(removeContentVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        int i = apiContentMapper.deleteById(removeContentVo.getId());

        // 更新热点项目缓存
        if (i > 0) {
            updateContentCacheIfHot(removeContentVo.getProjectId(), removeContentVo.getId());
        }

    }

    @Override
    public void renameContent(RenameContentVo renameContentVo, Long userId) {
        if (!projectService.hasPermissionToEdit(renameContentVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        ApiContent apiContent = new ApiContent();
        apiContent.setId(renameContentVo.getId());
        apiContent.setLabel(renameContentVo.getLabel());
        int i = apiContentMapper.updateById(apiContent);

        // 更新热点项目缓存
        if (i > 0) {
            updateContentCacheIfHot(renameContentVo.getProjectId(), renameContentVo.getId());
        }

    }

    @Override
    public ApiDataVo getApiData(TwoIdVo twoIdVo, Long userId) {

        // 先查询是否是热点项目
        Object o = redisTemplate.opsForHash().get(RedisConstant.PROJECT_HOT_KEY + twoIdVo.getProjectId(),
                RedisConstant.PROJECT_HOT_API_KEY + twoIdVo.getId());
        if (o != null) {
            return (ApiDataVo) o;
        }

        Project project = projectService.getById(twoIdVo.getProjectId());
        if (project.getIsPrivate() == 1) {
            if (userId == null || !projectService.hasPermissionToEdit(twoIdVo.getProjectId(), userId))
                throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        Api api = this.getById(twoIdVo.getId());
        if (api == null || !Objects.equals(api.getProjectId(), twoIdVo.getProjectId())) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }


        ApiDataVo apiDataVo = new ApiDataVo();

        apiDataVo.setUrl(api.getUrl());
        apiDataVo.setMethod(api.getMethod());
        apiDataVo.setDescription(api.getDescription());

        List<ApiContent> apiContents = apiContentMapper.selectList(new LambdaQueryWrapper<ApiContent>()
                .eq(ApiContent::getApiId, twoIdVo.getId())
                .orderByAsc(ApiContent::getOrder));

        apiContents = apiContents == null ? new ArrayList<>(0) : apiContents;

        apiContents.forEach(apiContent -> apiContent.setApiId(null));

        apiDataVo.setContents(apiContents);

        return apiDataVo;
    }

    @Transactional
    @Override
    public void saveApiData(ApiDataVo apiDataVo, Long userId) {

        Object editObj = redisTemplate.opsForValue().get("startEdit:" + apiDataVo.getProjectId() + ":" + DocumentConstant.API_TYPE + ":" + apiDataVo.getApiId());
        if (editObj == null || !editObj.toString().equals(String.valueOf(LoginContextHandler.getUserId()))) {
            throw new CustomException(R.instance(BizCodeEnum.DOC_NOT_IN_EDIT_ERROR));
        }

        Api api = this.getById(apiDataVo.getApiId());

        if (api == null || !Objects.equals(api.getProjectId(), apiDataVo.getProjectId()) || !projectService.hasPermissionToEdit(apiDataVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        // 更新Api 基本信息
        boolean needUpdate = false;
        LambdaUpdateWrapper<Api> updateWrapper = new LambdaUpdateWrapper<>();

        if (!Objects.equals(api.getUrl(), apiDataVo.getUrl())) {
            updateWrapper.set(Api::getUrl, apiDataVo.getUrl());
            needUpdate = true;
        }

        if (!Objects.equals(api.getMethod(), apiDataVo.getMethod())) {
            updateWrapper.set(Api::getMethod, apiDataVo.getMethod());
            needUpdate = true;
        }

        if (!Objects.equals(api.getDescription(), apiDataVo.getDescription())) {
            updateWrapper.set(Api::getDescription, apiDataVo.getDescription());
            needUpdate = true;
        }

        if (needUpdate) {
            this.update(updateWrapper.eq(Api::getId, apiDataVo.getApiId()));
        }

        AtomicBoolean update = new AtomicBoolean(false);
        // 更新内容
        apiDataVo.getContents().forEach(apiContent -> {
            LambdaUpdateWrapper<ApiContent> wrapper = new LambdaUpdateWrapper<ApiContent>()
                    .eq(ApiContent::getId, apiContent.getId())
                    .set(ApiContent::getLabel, apiContent.getLabel())
                    .set(ApiContent::getOrder, apiContent.getOrder());

            if (apiContent.getType() == 0) {
                wrapper.set(ApiContent::getParams, JSON.toJSONString(apiContent.getParams()));
            } else {
                wrapper.set(ApiContent::getContent, apiContent.getContent());
            }

            boolean u = apiContentService.update(wrapper);
            if (!update.get() && u)
                update.set(true);
        });

        // 更新热点项目缓存
        if (update.get() || needUpdate)
            updateContentCacheIfHot(apiDataVo.getProjectId(), apiDataVo.getApiId());

    }

    /**
     * 更新内容缓存
     *
     * @param projectId
     * @param apiId
     */
    private void updateContentCacheIfHot(long projectId, long apiId) {
        ApiDataVo apiData = getApiData(new TwoIdVo(apiId, projectId), LoginContextHandler.getUserId());
        HotProjectUtil.updateCacheIfHot(redisTemplate, projectId,
                RedisConstant.PROJECT_HOT_API_KEY + apiId, apiData);
    }

    @Transactional
    @Override
    public void clearProject(long projectId) {

        List<Long> apiIds = this.getIdsByProjectId(projectId);

        if (apiIds != null && !apiIds.isEmpty()) {
            // delete tag
            apiTagMapper.delete(new LambdaQueryWrapper<ApiTag>()
                    .in(ApiTag::getApiId, apiIds));

            // delete content
            apiContentMapper.delete(new LambdaQueryWrapper<ApiContent>()
                    .in(ApiContent::getApiId, apiIds));

            // 删除api
            this.remove(new LambdaQueryWrapper<Api>()
                    .eq(Api::getProjectId, projectId));

        }
    }

    public List<Long> getIdsByProjectId(long projectId) {
        return this.getBaseMapper().getIdsByProjectId(projectId);
    }


}




